﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Net;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using SABnzbdNET.Model;

namespace SABnzbdNET.ServiceAgents
{
    public class ServerAgent
    {
        private readonly BackgroundWorker backgroundWorker; 
        private Action<ServerInfo, string> getInfoCallback;
        private Action<ServerHistory, string> getHistoryCallback;
        private Action<WarningList, string> getWarningsCallback;
        private Action runCommandCallback;
        private Action uploadCallback;

        public ServerAgent()
        {
            backgroundWorker = new BackgroundWorker();
            backgroundWorker.RunWorkerCompleted += BackgroundWorkerCompleted;
            backgroundWorker.DoWork += BackgroundWorkerDoWork;
            backgroundWorker.WorkerSupportsCancellation = true;
        }

        public void GetInfo(Action<ServerInfo, string> callback)
        {
            string error;
            if (!ServerAddressIsValid(out error))
            {
                callback(null, error);
                return;          
            }
            Uri serverUrl = GetServerUrl(Properties.Settings.Default.ServerPathMode_Status);

            getInfoCallback = callback;
            var client = new WebClient();
            client.OpenReadCompleted += GetInfoCompleted;
            client.OpenReadAsync(serverUrl);
        }

        private void GetInfoCompleted(object sender, OpenReadCompletedEventArgs e)
        {
          try
            {
                // Create a new StreamReader instance using the specified Stream object.
                using (var streamreader = new StreamReader(e.Result))
                {
                    string xml = streamreader.ReadToEnd();
                    if (!String.IsNullOrEmpty(xml))
                    {
                        try
                        {
                          var serverInfo = DeserializeObject<ServerInfo>(xml);
                          getInfoCallback(serverInfo, string.Empty);
                        }
                        catch
                        {
                            var error = DeserializeObject<Error>(xml);
                            getInfoCallback(null, error.ErrorDescription);
                        }
                    }
                    else
                        getInfoCallback(null, "No response from server");
                }
            }
            catch (Exception ex)
            {
                getInfoCallback(null, GetInnerException(ex).Message);
            }
        }

        public void GetHistory(Action<ServerHistory, string> callback)
        {
            string error;
            if (!ServerAddressIsValid(out error))
            {
                callback(null, error);
                return;
            }
            Uri serverUrl = GetServerUrl(Properties.Settings.Default.ServerPathMode_History, "0", "10");

            getHistoryCallback = callback;
            var client = new WebClient();
            client.OpenReadCompleted += GetHistoryCompleted;
            client.OpenReadAsync(serverUrl);
        }

        private void GetHistoryCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            try
            {
                // Create a new StreamReader instance using the specified Stream object.
                using (var streamreader = new StreamReader(e.Result))
                {
                    string xml = streamreader.ReadToEnd();
                    if (!String.IsNullOrEmpty(xml))
                    {
                        try
                        {
                            var serverHistory = DeserializeObject<ServerHistory>(xml);
                            getHistoryCallback(serverHistory, string.Empty);
                        }
                        catch
                        {
                            var error = DeserializeObject<Error>(xml);
                            getHistoryCallback(null, error.ErrorDescription);
                        }
                    }
                    else
                        getHistoryCallback(null, "No response from server");
                }
            }
            catch (Exception ex)
            {
                getHistoryCallback(null, GetInnerException(ex).Message);
            }
        }

        public void GetWarnings(Action<WarningList, string> callback)
        {
            string error;
            if (!ServerAddressIsValid(out error))
            {
                callback(null, error);
                return;
            }
            Uri serverUrl = GetServerUrl(Properties.Settings.Default.ServerPathMode_Warnings);

            getWarningsCallback = callback;
            var client = new WebClient();
            client.OpenReadCompleted += GetWarningCompleted;
            client.OpenReadAsync(serverUrl);
        }

        private void GetWarningCompleted(object sender, OpenReadCompletedEventArgs e)
        {
          try
            {
                // Create a new StreamReader instance using the specified Stream object.
                using (var streamreader = new StreamReader(e.Result))
                {
                    string xml = streamreader.ReadToEnd();
                    if (!String.IsNullOrEmpty(xml))
                    {
                        try
                        {
                          var warningsList = DeserializeObject<WarningList>(xml);
                          getWarningsCallback(warningsList, string.Empty);
                        }
                        catch
                        {
                            var error = DeserializeObject<Error>(xml);
                            getWarningsCallback(null, "Error: " + error.ErrorDescription);
                        }
                    }
                    else
                        getWarningsCallback(null, "Error: No response from server");
                }
            }
            catch (Exception ex)
            {
                getWarningsCallback(null, GetInnerException(ex).Message);
            }
        }

        public void StartServer(Action callback, Action<string> errorCallback)
        {
            Uri serverUrl = GetServerUrl(Properties.Settings.Default.ServerPathMode_Start);
            RunCommand(serverUrl, callback, errorCallback);
        }

        public void PauseServer(Action callback, Action<string> errorCallback)
        {
            Uri serverUrl = GetServerUrl(Properties.Settings.Default.ServerPathMode_Stop);
            RunCommand(serverUrl, callback, errorCallback);
        }

        public void ShutDownServer(Action callback, Action<string> errorCallback)
        {
            Uri serverUrl = GetServerUrl(Properties.Settings.Default.ServerPathMode_Shutdown);
            RunCommand(serverUrl, callback, errorCallback);
        }

        public void UploadToServer(string fileName, string safeFileName, string newzBinId, string category
            , string script, string priority, Action callback)
        {
            string serverUrlAddFile = GetServerUrl(Properties.Settings.Default.ServerPathMode_AddFile,
                safeFileName, category, script, priority).ToString();

            string serverUrlAddId = GetServerUrl(Properties.Settings.Default.ServerPathMode_AddId,
                newzBinId, category, script, priority).ToString();

            uploadCallback = callback;
            backgroundWorker.RunWorkerAsync(new List<string> 
            { 
                serverUrlAddFile, 
                fileName, 
                serverUrlAddId, 
                newzBinId 
            });
        }

        public void CancelUploadToServer()
        {
            if (backgroundWorker.IsBusy)
                backgroundWorker.CancelAsync();
        }

        public void StartDownload(string id, Action callback, Action<string> errorCallback)
        {
            Uri serverUrl = GetServerUrl(Properties.Settings.Default.ServerPathMode_StartSingle, id);
            RunCommand(serverUrl, callback, errorCallback);
        }

        public void PauseDownload(string id, Action callback, Action<string> errorCallback)
        {
            Uri serverUrl = GetServerUrl(Properties.Settings.Default.ServerPathMode_StopSingle, id);
            RunCommand(serverUrl, callback, errorCallback);
        }

        public void DeleteDownload(string id, Action callback, Action<string> errorCallback)
        {
            Uri serverUrl = GetServerUrl(Properties.Settings.Default.ServerPathMode_DeleteSingle, id);
            RunCommand(serverUrl, callback, errorCallback);
        }

        public void ChangeProcessingForDownload(string id, string processing, Action callback, Action<string> errorCallback)
        {
            Uri serverUrl = GetServerUrl(Properties.Settings.Default.ServerPathMode_ChangeProcessing, 
                id, GetProcessing(processing).ToString());
            RunCommand(serverUrl, callback, errorCallback);
        }

        public void ChangePriorityForDownload(string id, string priority, Action callback, Action<string> errorCallback)
        {
            Uri serverUrl = GetServerUrl(Properties.Settings.Default.ServerPathMode_ChangePriority, 
                id, GetPriority(priority).ToString());
            RunCommand(serverUrl, callback, errorCallback);
        }

        public void DeleteFinishedDownload(string id, Action callback, Action<string> errorCallback)
        {
            Uri serverUrl = GetServerUrl(Properties.Settings.Default.ServerPathMode_DeleteFinishedSingle, id);
            RunCommand(serverUrl, callback, errorCallback);
        }

        private void RunCommand(Uri command, Action callback, Action<string> errorCallback)
        {
            string error;
            if (!ServerAddressIsValid(out error))
            {
                errorCallback(error);
                return;
            }
            
            runCommandCallback = callback;
            var client = new WebClient();
            client.OpenReadCompleted += RunCommandCompleted;
            client.OpenReadAsync(command);
        }

        private void RunCommandCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            runCommandCallback();
        }

        private static void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            //upload file
            var data = e.Argument as List<string>;
            if (data == null) return;
            if (!string.IsNullOrEmpty(data[1]))
                UploadFile(data[0], data[1]);
            if (!string.IsNullOrEmpty(data[3]))
                UploadId(data[2]);
        }

        private void BackgroundWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            uploadCallback();
        }

        private static void UploadFile(string url, string file)
        {
            string bndy = "--" + Guid.NewGuid();

            var wr = (HttpWebRequest)WebRequest.Create(url);
            wr.Method = "POST";
            wr.ContentType = "multipart/form-data; boundary=" + bndy.Substring(2);
            using (var strm = wr.GetRequestStream())
            {
                using (var sr = new StreamWriter(strm, new UTF8Encoding(false)))
                {
                    // Write "FILE".
                    sr.WriteLine(bndy);
                    sr.WriteLine("Content-Disposition: form-data; name=\"name\"; filename=\"" + Path.GetFileName(file) + "\"");
                    sr.WriteLine("Content-Type: application/x-nzb");
                    sr.WriteLine();
                    sr.Flush();
                    CopyFileToStream(file, strm);
                    strm.Flush();
                    sr.WriteLine();
                    // Write last boundary.
                    sr.Write(bndy);
                    sr.WriteLine("--");
                }
            }
            wr.GetResponse();
        }

        private static void UploadId(string url)
        {
            var client = new WebClient();
            client.OpenRead(url);
        }

        private static void CopyFileToStream(string fileName, Stream strm)
        {
            using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                var buf = new byte[8192];
                while (true)
                {
                    int bytesRead = fs.Read(buf, 0, buf.Length);
                    if (bytesRead == 0) // EOF
                    {
                        break;
                    }
                    strm.Write(buf, 0, bytesRead);
                }
            }
        }

        private static int GetProcessing(string processing)
        {
            switch (processing.ToLower())
            {
                case "download":
                    return 0;
                case "+repair":
                    return 1;
                case "+unpack":
                    return 2;
                case "+delete":
                    return 3;
            }
            return 0;
        }

        private static int GetPriority(string priority)
        {
            switch (priority.ToLower())
            {
                case "low":
                    return -1;
                case "normal":
                    return 0;
                case "high":
                    return 1;
            }
            return 0;
        }

        private static bool ServerAddressIsValid(out string error)
        {
            error = string.Empty;
            try
            {
                var uri = new Uri(Properties.Settings.Default.ServerPath);
                var test = uri.IsWellFormedOriginalString();
            }
            catch (Exception ex)
            {
                error = string.Format("Invalid server address ({0}).", ex.Message);
                return false;
            }
            return true;
        }

        private static Uri GetServerUrl(string serverPathMode, params string[] args)
        {
            string serverPath = Properties.Settings.Default.ServerPath;
            if(!serverPath.EndsWith("/"))
                serverPath = serverPath + "/";

            string[] allParams = new string[]{
                Properties.Settings.Default.ServerUsername,
                Properties.Settings.Default.ServerPassword,
                Properties.Settings.Default.ServerAPIKey};
            Array.Resize(ref allParams, allParams.Length + args.Length);
            Array.Copy(args, 0, allParams, allParams.Length - args.Length, args.Length);
            string url = string.Format(serverPath + serverPathMode + Properties.Settings.Default.ServerPathParameters, allParams);
            return new Uri(url, UriKind.Absolute);
        }

        private static Exception GetInnerException(Exception ex)
        {
            while (ex.InnerException != null)
                ex = ex.InnerException;
            return ex;
        }

        private static T DeserializeObject<T>(string xml)
        {
            var xs = new XmlSerializer(typeof(T));
            var memoryStream = new MemoryStream(StringToUtf8ByteArray(xml));
            new XmlTextWriter(memoryStream, Encoding.UTF8);
            return (T)xs.Deserialize(memoryStream);
        }

        private static Byte[] StringToUtf8ByteArray(string pXmlString)
        {
            var encoding = new UTF8Encoding();
            byte[] byteArray = encoding.GetBytes(pXmlString);
            return byteArray;
        }
    }
}
